/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * TrainingBook
 * Copyright (C) Arnaud Duval 2010 <arnaud.duval@gmail.com>
 * 
 * TrainingBook is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * TrainingBook is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "osm-map-tile-cache.hpp"
#include <config.hpp>
#include <sstream>
#include <fstream>
#include <iostream>
#include <curl/curl.h>
#include <cstdlib>
#include <unistd.h>
#include <cmath>
#include <filesystem-utils.hpp>




///@brief Default constructor
OsmMapTileCache::OsmMapTileCache()
{
    statusBar = NULL;
}

///@brief Constructor specifying the cache location
///@param p Path to the cache directory
///@param tilesRepository Path to the distant tiles repository. Should end with a "/"
OsmMapTileCache::OsmMapTileCache(std::string p, std::string tilesRepository)
{
    cachePath = p;
    tilesRepositoryPath = tilesRepository;
    //Removing the http:// substring in tiles repository if present
    std::string tilesRepoNoHttp = tilesRepositoryPath;
    size_t found=tilesRepoNoHttp.find("http://");
    if(found != std::string::npos)
    {
      if(found == 0)
      {
        tilesRepoNoHttp = tilesRepoNoHttp.substr(7, tilesRepoNoHttp.length());
      }
    }
    cachePathRepo = cachePath+TRAININGBOOK_FILE_SEPARATOR+tilesRepoNoHttp;
    EnsureCachePath(cachePathRepo);
    statusBar = NULL;
}

///@brief Destructor
OsmMapTileCache::~OsmMapTileCache()
{

}

///@brief Return the path to a specified tile
///@param X X coordinate in the Mercator projection
///@param Y Y coordinate in the Mercator projection
///@param Z Zoom level
std::string OsmMapTileCache::GetTile(int X, int Y, int Z)
{
    bool tileOK = true;
    unsigned int n = pow(2, Z);
    if(X < 0)
        X = n - X + 1;
    if(Y < 0)
        Y = n - Y + 1;
    if(X >= n)
        X = n-1;
    if(Y >= n)
        Y = n-1;
    if(!IsInCache(X, Y, Z))
        tileOK = DownloadCache(X, Y, Z);

    if(tileOK)
    {
        std::string tileFileLocal = cachePathRepo + TRAININGBOOK_FILE_SEPARATOR;
        //std::string tileFileHttp = "http://tile.openstreetmap.org/";
        std::string tileFileHttp = tilesRepositoryPath;
        std::ostringstream zoomStream, xStream, yStream;
        zoomStream << Z;
        xStream << X;
        yStream << Y;

        return tileFileLocal + zoomStream.str() + TRAININGBOOK_FILE_SEPARATOR + xStream.str() + TRAININGBOOK_FILE_SEPARATOR + yStream.str() + ".png";
    }
    else
    {
        ///@warning The location of 404 tile should be defined by tileCache object
        //return std::string(TRAININGBOOK_USER_DIR)+TRAININGBOOK_FILE_SEPARATOR+"cache"+TRAININGBOOK_FILE_SEPARATOR+"404.png";
        ///@bug 404.png file is not includedd in software distribution
        return cachePathRepo+TRAININGBOOK_FILE_SEPARATOR+"404.png";
    }
}

///@brief Dowload a specified tile to the cache
///@param X X coordinate in the Mercator projection
///@param Y Y coordinate in the Mercator projection
///@param Z Zoom level
///@return true if everything is OK
bool OsmMapTileCache::DownloadCache(int X, int Y, int Z)
{
    bool problem = false;
    std::string tileFileLocal = cachePathRepo + TRAININGBOOK_FILE_SEPARATOR;
    //std::string tileFileHttp = "http://tile.openstreetmap.org/";
    std::string tileFileHttp = tilesRepositoryPath;

    std::ostringstream zoomStream, xStream, yStream;
    zoomStream << Z;
    xStream << X;
    yStream << Y;
	
    if(access ((cachePathRepo + TRAININGBOOK_FILE_SEPARATOR + zoomStream.str()).c_str(), W_OK) != 0)
    {
		std::cout << "Creating " << cachePathRepo + TRAININGBOOK_FILE_SEPARATOR + zoomStream.str() << "\n";
		CreateDir(cachePathRepo + TRAININGBOOK_FILE_SEPARATOR + zoomStream.str());
    }

    if(access ((cachePathRepo + TRAININGBOOK_FILE_SEPARATOR+ zoomStream.str()+ TRAININGBOOK_FILE_SEPARATOR + xStream.str()).c_str(), W_OK) != 0)
    {
        std::cout << "Creating " << cachePathRepo + TRAININGBOOK_FILE_SEPARATOR + zoomStream.str() + TRAININGBOOK_FILE_SEPARATOR + xStream.str() << "\n";
		CreateDir(cachePathRepo + TRAININGBOOK_FILE_SEPARATOR + zoomStream.str() + TRAININGBOOK_FILE_SEPARATOR + xStream.str());
    }


    if(statusBar)
    {
        statusBar->push("Downloading file "+ tileFileHttp + zoomStream.str() + "/" + xStream.str() + "/" + yStream.str()
                      + ".png ...");
        ///@warning We should ensure that is message is displayed in the status bar
    }
    else
    {
      std::cout << "Downloading file "<< tileFileHttp << zoomStream.str() + "/" + xStream.str() + "/" + yStream.str()
                      + ".png ...";
    }
	
	
	
	
    std::cout.flush();
    tileFileLocal = tileFileLocal + zoomStream.str() + TRAININGBOOK_FILE_SEPARATOR + xStream.str() + TRAININGBOOK_FILE_SEPARATOR + yStream.str() + ".png";
    tileFileHttp = tileFileHttp + zoomStream.str() + "/" + xStream.str() + "/" + yStream.str() + ".png";

    CURL *curl;
    CURLcode res;
    FILE *outfile;
        
    curl = curl_easy_init();
    
    if(curl)
    {
        outfile = fopen(tileFileLocal.c_str(), "wb");
        
        curl_easy_setopt( curl, CURLOPT_URL, tileFileHttp.c_str() ) ;
        curl_easy_setopt( curl, CURLOPT_WRITEDATA, outfile ) ;
      
        res = curl_easy_perform(curl);
        fclose(outfile);
        curl_easy_cleanup( curl );
        
        if(res != 0)
        {
            problem = true;
        }
    }
    else
    {
        std::cout << "Problem while initilizing curl\n";
    }
    
    if(problem)
    {
        RemoveFile(tileFileLocal);
    }
    if(statusBar)
    {
      if(!problem)
        statusBar->push("OK");
      else
        statusBar->push("Error");
    }
    else
    {
      if(!problem)
        std::cout << "OK\n";
      else
        std::cout << "Error\n";
    }

    return !problem;
}
///@brief Test if the specified tile is present in the cache
///@param X X coordinate in the Mercator projection
///@param Y Y coordinate in the Mercator projection
///@param Z Zoom level
bool OsmMapTileCache::IsInCache(int X, int Y, int Z)
{
    std::string tileFile = cachePathRepo + TRAININGBOOK_FILE_SEPARATOR;
    std::ostringstream zoomStream, xStream, yStream;
    zoomStream << Z;
    xStream << X;
    yStream << Y;
    tileFile = tileFile + zoomStream.str() + TRAININGBOOK_FILE_SEPARATOR + xStream.str() + TRAININGBOOK_FILE_SEPARATOR + yStream.str() + ".png";
    std::ifstream fichier(tileFile.c_str());
    return !fichier.fail(); 
}

///@brief Set the tiles repository URL
///@param osmTilesRepository URL of the tiles repository to use
///@warning Tile cache must be set before setting the tiles repository
void OsmMapTileCache::SetTilesRepository(std::string osmTilesRepository)
{
    tilesRepositoryPath = osmTilesRepository;

    //Removing the http:// substring if present
    std::string tilesRepoNoHttp = tilesRepositoryPath;
    size_t found=tilesRepoNoHttp.find("http://");
    if(found != std::string::npos)
    {
      if(found == 0)
      {
        tilesRepoNoHttp = tilesRepoNoHttp.substr(7, tilesRepoNoHttp.length());
      }
    }
    cachePathRepo = cachePath+TRAININGBOOK_FILE_SEPARATOR+tilesRepoNoHttp;
    EnsureCachePath(cachePathRepo);
}

///@brief Set the tiles cache URL
///@param osmTilesCache Path to the tiles cache
///@warning Tile cache must be set before setting the tiles repository
void OsmMapTileCache::SetTilesCache(std::string osmTilesCache)
{
    cachePath = osmTilesCache;
}


///@brief Ensure that the cache directory corresponding to current directory is present
///@param path directory to control and create if not present
void OsmMapTileCache::EnsureCachePath(std::string path)
{
	if(access ((path + TRAININGBOOK_FILE_SEPARATOR).c_str(), W_OK) != 0)
    {
        //Retrieve the path to parent directory
        std::string parentPath = GetParentPath(path);
        EnsureCachePath(parentPath);

        CreateDir(path + TRAININGBOOK_FILE_SEPARATOR);
    }
}

///@brief Return the parent directory of a given path
///@param path Path directory from which the parent directory should be retrieved
std::string OsmMapTileCache::GetParentPath(std::string path)
{
    std::cout << "GetParent of " << path << "\n";
    size_t found = path.rfind(TRAININGBOOK_FILE_SEPARATOR);
    if(found != std::string::npos)
    {
        std::string res = path.substr(0, found);
        std::cout << "==> " << res <<"\n";
        return res;
    }
    else
        return TRAININGBOOK_FILE_SEPARATOR;

}

///@brief Set the status bar
///@param pBar Pointer ti the status bar that should be set
void OsmMapTileCache::SetStatusBar(Gtk::Statusbar* pBar)
{
    statusBar = pBar;
}

///@brief Overload of << operator
///@param s Out stream
///@param cache OsmMapTileCache object to put in the out stream
std::ostream& operator<< (std::ostream& s, const OsmMapTileCache& cache)
{
    ///@brief Print the state of the cache
    s << "Current tiles cache :\n";
    s << "\tLocal directory : \t" << cache.cachePathRepo << "\n";
    s << "\tDistant repository : \t" << cache.tilesRepositoryPath << "\n";

    //TODO

    return s;
}
